﻿using System;
using System.Collections.Generic;
using System.Text;

using Galaxy.ModuleInterface;
using Galaxy.ModuleInterface.Messages;
using Galaxy.Portal.Messages;

using System.Net.Sockets;
using TCPSockets;
using TCPSockets.ServerEvents.Args;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;

namespace Galaxy.Portal.TCP
{
    /// <summary>Портал по протоколу TCP</summary>
    public class CTCPPortal : CGalaxyPortalModule, IPortal, IGalaxyModule
    {
        /* -------------------------------------------------------------------- */

        private CTCPServer f_TCPServer;

        private List<CIncomingConnection> f_IncomingConnections = new List<CIncomingConnection>();

        private IFormatter f_Formatter = new BinaryFormatter();

        private List<CKnownConnection> f_KnownConnection = new List<CKnownConnection>();

        /* -------------------------------------------------------------------- */

        protected virtual CTCPServer TCPServer { get { return f_TCPServer; } }

        public override bool Initialized { get { return TCPServer != null; } }

        public override bool Enable
        {
            get { return Initialized && TCPServer.Enabeld; }
            set
            {
                base.Enable = value;
                if(value) Start(); else Stop();
            }
        }

        /* -------------------------------------------------------------------- */

        public CTCPPortal() : base("Портал TCP") { }

        public CTCPPortal(string Name) : base(Name) { }

        /* -------------------------------------------------------------------- */

        public override void Start()
        {
            if(Enable) return;

            TCPServer.Enabeld = true;

        }

        public override void Stop()
        {
            if(!Enable) return;
            TCPServer.Enabeld = false;
        }

        public override void Initialize()
        {
            if(Initialized) return;

            f_TCPServer = new CTCPServer(Port);
            ConnectTCPServer(f_TCPServer);
        }

        private void ConnectTCPServer(CTCPServer Server)
        {
            if(Server == null) throw new ArgumentNullException("Не задан сервер");
            Server.OnClientConnected += OnClientConnectedToServer;
        }        
        
        private void DisconnectTCPServer(CTCPServer Server)
        {
            if(Server == null) throw new ArgumentNullException("Не задан сервер");
            Server.OnClientConnected -= OnClientConnectedToServer;
        }

        private void OnClientConnectedToServer(CTCPServer Server, OnClientConnectedEventArgs Args)
        {
            CIncomingConnection lv_NewConnection;
            f_IncomingConnections.Add(lv_NewConnection = new CIncomingConnection(Args.Client, this));
            lv_NewConnection.OnConnectionComplite += OnIncomingConnectionComplite;
        }

        private void OnIncomingConnectionComplite(CIncomingConnection Connection, 
            CIncomingConnection.OnConnectionCompliteEventArgs Arg)
        {
            f_IncomingConnections.Remove(Connection);
            Connection.OnConnectionComplite -= OnIncomingConnectionComplite;

            try
            {
                ProcessIncomingObject(f_Formatter.Deserialize(Arg.DataStream));
            }
            catch(SerializationException)
            {
                ProcessIncomingBinaryMessage(Arg.DataStream);
            }
        }

        private void ProcessIncomingBinaryMessage(Stream DataStream)
        {
            InvokeNewMessage(new CTypedMessage<Stream>(DataStream, this));
        }

        private void ProcessIncomingObject(object Object)
        {
            if(Object is IGalaxyModuleMessage)
                ProcessReceivedModuleMessage(Object as IGalaxyModuleMessage);
        }  

        private void ProcessReceivedModuleMessage(IGalaxyModuleMessage Message)
        {
            InvokeNewMessage(Message);
        }

        public override void ProcessMessage(IGalaxyModuleMessage Message)
        {
            base.ProcessMessage(Message);
            SendMessage(Message);
        }

        public override void SendMessage(IGalaxyModuleMessage Message)
        {
            if(!Enable || !CTCPClient.CheckSerializable(Message)) return;

            if(Message.Destination is INetDestination)
                SingleCastMessage(Message);
            else
                MultiCastMessage(Message);

        }

        private void MultiCastMessage(IGalaxyModuleMessage Message)
        {
            TCPServer.ForEachClient(delegate(CTCPClient Client) { Client.SendObject(Message); });
        }

        private void SingleCastMessage(IGalaxyModuleMessage Message)
        {
            INetDestination lv_Destination = Message.Destination as INetDestination;
            if(lv_Destination == null || !Enable) return;

            CKnownConnection lv_Connection = f_KnownConnection.Find(delegate(CKnownConnection Connection)
            {
                return lv_Destination.Address == Connection.Address &&
                    lv_Destination.Port == Connection.Port;
            });

            if(lv_Connection == null)
                f_KnownConnection.Add(lv_Connection = 
                    new CKnownConnection(lv_Destination.Address, lv_Destination.Port));

            using(CTCPClient lv_Client = lv_Connection)
                try
                {
                    lv_Client.Connected = true;
                    lv_Client.SendObject(Message);
                }
                finally
                {
                    lv_Client.Connected = false;
                }
        }


    }
}
